#ifndef MODEL_H
#define MODEL_H
#include "Mesh.h"
#include "Skeleton.h"
#include "Bone.h"
#include "ModelAnimation.h"
#include <Cache.h>
#include <IShader.h>
#include <assimp/scene.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/euler_angles.hpp>
#include <list>
#include <memory>
#include <string>
#include <vector>

class Skeleton;

struct boneInfo
{
    std::string name;
    int id;
    int parentId;
    glm::mat4 restPose;
    glm::mat4 bindPose;
    glm::mat4 restPoseInv;

    boneInfo(std::string name, int id, int parentId, glm::mat4 restPose, glm::mat4 bindPose, glm::mat4 restPoseInv)
        : name(name), id(id), parentId(parentId), restPose(restPose), bindPose(bindPose), restPoseInv(restPoseInv)
    {
    }
};

class ModelAsset {
public:
    explicit ModelAsset(const std::string& path);
    void Draw(const gl::IShader& shader, int instanceCount) const;
    void Play(const std::string& animName, uint32_t frame, const std::string& animName2, float blend, glm::mat4 model);

    int GetBoneIndex(const std::string& boneName)
    {
        if (boneNameIndex.find(boneName) != boneNameIndex.end())
            return boneNameIndex[boneName];
        return -1;
    }

    std::shared_ptr<SkeletonAsset> skeleton;

    std::unordered_map<uint32_t, glm::mat4> boneOffsetCache;
    std::shared_ptr<std::vector<std::string>> animNames;
    void CreateCombinedMesh(bool combineCloth = false, std::string clothName = "combined");

private : 
    std::string filePath;
    std::vector<MeshAsset> meshes;
    std::string directory;

    std::string SkeletonName;
    std::unordered_map<std::string, uint32_t> boneNameIndex;
    std::unordered_map<int, std::string> boneIdtoNames;

    glm::mat4 animGlobalInverseT {};
    std::unordered_map<std::string, std::shared_ptr<AnimationAsset>> cachedAnim;
    std::list<std::shared_ptr<AnimationAsset>> animations;

    void BlendFrameSRT(std::shared_ptr<std::vector<SRT>> frameA,
                       std::shared_ptr<std::vector<SRT>> frameB,
                       float alpha,
                       std::shared_ptr<std::vector<glm::mat4>> out);
    void BlendFrameMat(std::shared_ptr<std::vector<glm::mat4>> frameA,
                       std::shared_ptr<std::vector<glm::mat4>> frameB,
                       float alpha,
                       std::shared_ptr<std::vector<glm::mat4>> out);

    void LoadModel(const std::string& path);
    Texture LoadMaterialTextures(const aiScene* scene, aiMaterial* material, aiTextureType type, const std::string& typeName);
    void LoadAnimations(const aiScene* scene);
    void LoadMeshes(const aiScene* scene);

    void BoneTransform(const aiNode *root, aiAnimation *anim, float timeInMs,
                       std::vector<SRT> &animSRTs, std::vector<glm::mat4> &animMat4s);
    void NodeAnimInner(aiAnimation *anim, float animationTime, const aiNode *node,
                       std::vector<SRT> &animSRTs, std::vector<glm::mat4> &animMat4s);
    static const aiNodeAnim* FindNodeAnim(const aiAnimation* animation, const std::string& nodeName);
};



#endif //MODEL_H
